package xj.graph2d.graph;

import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.util.Iterator;
import java.util.Set;

public class GraphForceLayout extends GraphSimpleLayout {

  public GraphForceLayout(Graph g) {
    super(g);
  }

  static final int maxIteration = 50;

  static final int maxYRange = 800;

  static final int maxXRange = 1200;

  static final int maxYForce = 20;

  static final int maxXForce = 20;

  @Override
  public void doLayout() {
    if (graph != null) {
      int count = 0;
      boolean stable = false;
      while (!stable && count < maxIteration) {

	// System.out.println("ForceLayout: count = " + count);

	count++;
	stable = true;
	Iterator nodes1 = graph.nodes();
	while (nodes1.hasNext()) {
	  GraphNode n1 = (GraphNode) nodes1.next();
	  if (n1 != null) {
	    Point2D c1 = n1.getCenter();
	    double xForce = 0.0;
	    double yForce = 0.0;
	    Iterator nodes2 = graph.nodes();
	    while (nodes2.hasNext()) {
	      GraphNode n2 = (GraphNode) nodes2.next();
	      if (n2 != null && n1 != n2) {
		Point2D c2 = n2.getCenter();
		double f = force(n1, n2);
		if (c1 != null && c2 != null) {
		  double d = Geometry.distance(c1, c2);
		  double dx = c2.getX() - c1.getX();
		  double dy = c2.getY() - c1.getY();
		  xForce += f * dx / d;
		  yForce += f * dy / d;
		}
		/*
                 * double a = angle(n1, n2); boolean flip = false; if (c1 !=
                 * null && c2 != null && c1.x > c2.x) flip = true; if (flip) {
                 * xForce -= f*Math.cos(a); } else { xForce += f*Math.cos(a); }
                 * yForce += f*Math.sin(a);
                 */
	      }
	    }
	    double dx = (int) xForce;
	    double dy = (int) yForce;

	    double c1x = c1.getX();
	    double c1y = c1.getY();
	    if (c1 != null) {
	      if (dx < -c1x) {
		dx = -c1x;
	      }
	      if (dx > maxXRange - c1x) {
		dx = maxXRange - c1x;
	      }
	      if (dy < -c1y) {
		dy = -c1y;
	      }
	      if (dy > maxYRange - c1y) {
		dy = maxYRange - c1y;
	      }
	    }

	    if (dx < -maxXForce) {
	      dx = -maxXForce;
	    }
	    if (dx > maxXForce) {
	      dx = maxXForce;
	    }
	    if (dy < -maxYForce) {
	      dy = -maxYForce;
	    }
	    if (dy > maxYForce) {
	      dy = maxYForce;
	    }

	    // System.out.println("ForceLayout: dx = " + dx + "; dy
	    // = " + dy);

	    n1.move(dx, dy);
	    if (dx > 5 || dy > 5) {
	      stable = false;
	    }
	  }
	}
      }

      setTopLeftMargin(50, 50);
      layoutEdges();
    }
  }

  public void relaxNode(GraphNode node, boolean vConstrained,
      boolean hConstrained) {

    if (graph != null && node != null) {
      int count = 0;
      boolean stable = false;
      Point2D c1 = node.getCenter();
      while (!stable && count < maxIteration) {
	double xForce = 0.0;
	double yForce = 0.0;
	Iterator nodes = graph.nodes();
	while (nodes.hasNext()) {
	  GraphNode n2 = (GraphNode) nodes.next();
	  if (n2 != null && node != n2) {
	    Point2D c2 = n2.getCenter();
	    double f = force(node, n2);
	    if (c1 != null && c2 != null) {
	      double d = Geometry.distance(c1, c2);
	      double dx = c2.getX() - c1.getX();
	      double dy = c2.getY() - c1.getY();
	      xForce += f * dx / d;
	      yForce += f * dy / d;
	    }
	  }
	}
	double dx = (int) xForce;
	double dy = (int) yForce;

	double c1x = c1.getX();
	double c1y = c1.getY();
	if (c1 != null) {
	  if (dx < -c1x) {
	    dx = -c1x;
	  }
	  if (dx > maxXRange - c1x) {
	    dx = maxXRange - c1x;
	  }
	  if (dy < -c1y) {
	    dy = -c1y;
	  }
	  if (dy > maxYRange - c1y) {
	    dy = maxYRange - c1y;
	  }
	}

	if (dx < -maxXForce) {
	  dx = -maxXForce;
	}
	if (dx > maxXForce) {
	  dx = maxXForce;
	}
	if (dy < -maxYForce) {
	  dy = -maxYForce;
	}
	if (dy > maxYForce) {
	  dy = maxYForce;
	}

	// System.out.println("ForceLayout: dx = " + dx + "; dy = " +
	// dy);

	if (vConstrained) {
	  dx = 0;
	}
	if (hConstrained) {
	  dy = 0;
	}
	node.move(dx, dy);
	if (dx > 5 || dy > 5) {
	  stable = false;
	}
      }
    }
  }

  protected double neutualDistance = 50.0;

  protected double forceFactor = 10.0;

  // minus: repulsive; plus: attractive
  protected double force(GraphNode node1, GraphNode node2) {
    if (node1 != null && node2 != null) {
      Point2D c1 = node1.getCenter();
      Point2D c2 = node2.getCenter();
      double d = Geometry.distance(c1, c2);
      Dimension2D dim1 = node1.getSize();
      Dimension2D dim2 = node2.getSize();
      double d1 = Math.max(dim1.getWidth(), dim1.getHeight());
      double d2 = Math.max(dim2.getWidth(), dim2.getHeight());
      double neutual = (d1 + d2) + neutualDistance;
      if (d < neutual / 2.0) {
	return (-neutual); // strong repulsive
      }
      double dd = (neutual - d);
      GraphEdge e = hasEdge(node1, node2);
      double force = dd * dd;
      if (e != null) {
	if (dd != 0.0) {
	  force /= forceFactor;
	  force *= e.getWeight();
	  force *= node1.getWeight();
	  force *= node2.getWeight();
	  if (d < neutual) {
	    force = -force; // repulsive
	  }
	}
	return force;
      } else {
	return -(1 / force); // repulsive
      }
    }
    return 0.0;
  }

  protected double angle(GraphNode node1, GraphNode node2) {
    if (node1 != null && node2 != null) {
      Point2D c1 = node1.getCenter();
      Point2D c2 = node2.getCenter();
      double dx = 0.0;
      double dy = 0.0;
      if (c1 != null && c2 != null) {
	dx = c1.getX() - c2.getX();
	dy = c1.getY() - c2.getY();
      }
      if (dx != 0.0 && dy != 0.0) {
	return Math.atan2(dy, dx);
      }
    }
    return Math.random() % (Math.PI * 2) - Math.PI;
  }

  protected GraphEdge hasEdge(GraphNode node1, GraphNode node2) {
    if (node1 != null && node2 != null) {
      Set edgeSet = graph.leavingEdges(node1);
      Iterator edges;
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge edge = (GraphEdge) edges.next();
	  if (edge != null) {
	    GraphNode des = edge.getDestination();
	    if (des == node2) {
	      return edge;
	    }
	  }
	}
      }

      edgeSet = graph.arrivingEdges(node1);
      if (edgeSet != null) {
	edges = edgeSet.iterator();
	while (edges.hasNext()) {
	  GraphEdge edge = (GraphEdge) edges.next();
	  if (edge != null) {
	    GraphNode src = edge.getSource();
	    if (src == node2) {
	      return edge;
	    }
	  }
	}
      }
    }
    return null;
  }

}
